﻿/* 
 * Copyright (c) 2011 Jeff Zhang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of 
 * this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation the rights to use, 
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace Json {
    public class JsonParser {
        private BufferedCharReader _internalReader;

        public JsonParser(TextReader reader) {
            if (reader == null)
                throw new ArgumentNullException("reader");

            _internalReader = new BufferedCharReader(reader);
        }

        public JsonParser(string jsonString) {
            if (jsonString == null || jsonString.Length == 0)
                throw new ArgumentNullException("jsonString");

            _internalReader = new BufferedCharReader(new StringReader(jsonString));
        }

        public IJsonObject Parse() {
            return readObject();
        }

        #region readObject

        /// <summary>
        /// Read a json object
        /// </summary>
        /// <returns></returns>
        private IJsonObject readObject() {
            skipSpace();

            if (_internalReader.More()) {
                char c = _internalReader.Next();
                _internalReader.Back();

                switch (c) {
                    case TerminalSymbol.LEFT_BRACE:
                        return readDictionay();
                    case TerminalSymbol.LEFT_SQUARE_BRACKET:
                        return readArray();
                    case TerminalSymbol.LEFT_ANGLE_BRACKET:
                        return readDateTime();
                    case TerminalSymbol.SINGLE_QUOTE:
                    case TerminalSymbol.DOUBLE_QUOTE:
                        return readString();
                    case 'n':
                        return readNull();
                    case 't':
                    case 'f':
                        return readBoolean();
                    case TerminalSymbol.POSITIVE:
                    case TerminalSymbol.NEGATIVE:
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        return readNumber();
                    default:
                        throw new SyntaxError("SyntaxError", _internalReader.GetString(10));
                }
            } else {
                return new JsonString("");
            }
        }

        #endregion

        #region read a number

        /// <summary>
        /// Read a number from raw string.
        /// </summary>
        /// <returns></returns>
        private JsonNumber readNumber() {
            CharBuffer buffer = new CharBuffer();
            char c = _internalReader.Next();

            buffer.Push(c);
            bool hasE = false;
            bool hasDot = false;
            bool hasFlag = false;

            char last = BufferedCharReader.EOF;

            while (_internalReader.More()) {
                c = _internalReader.Next();

                if (char.IsNumber(c)) {
                    buffer.Push(c);
                } else {
                    switch (c) {
                        case TerminalSymbol.e:
                        case TerminalSymbol.E:
                            if (!hasE) {
                                buffer.Push(c);
                                hasE = true;
                            } else {
                                throw new SyntaxError("The token \"E\" alrady exists.", _internalReader.GetString(10));
                            }
                            break;
                        case TerminalSymbol.DOT:
                            if (!hasDot) {
                                buffer.Push(c);
                                hasDot = true;
                            } else {
                                throw new SyntaxError("The token \".\" already exists.", _internalReader.GetString(10));
                            }
                            break;
                        case TerminalSymbol.POSITIVE:
                        case TerminalSymbol.NEGATIVE:
                            if (last == TerminalSymbol.E || last == TerminalSymbol.e) {
                                if (!hasFlag) {
                                    buffer.Push(c);
                                    hasFlag = true;
                                } else {
                                    throw new SyntaxError("The token \".\" already exists.", _internalReader.GetString(10));
                                }
                            } else {
                                throw new SyntaxError("The token \".\" already exists.", _internalReader.GetString(10));
                            }
                            break;

                        case TerminalSymbol.SPACE:
                        case TerminalSymbol.ENTER:
                        case TerminalSymbol.NEWLINE:
                        case TerminalSymbol.TABLE:
                            break;
                        case TerminalSymbol.COMMA:
                        //case TerminalSymbol.RIGHT_PARENTHESIS:
                        case TerminalSymbol.RIGHT_BRACE:
                        case TerminalSymbol.RIGHT_SQUARE_BRACKET:
                            _internalReader.Back();
                            goto EXIT_LOOP;
                        default:
                            throw new SyntaxError(string.Format("The token \"{0}\" is not expected.", c), _internalReader.GetString(10));
                    }
                }

                last = c;
            }

EXIT_LOOP:
            string str = buffer.GetString();

            return new JsonNumber(Number.Parse(str));
            
        }

        #endregion

        #region read a bool

        /// <summary>
        /// Read a bool from raw string.
        /// </summary>
        /// <returns></returns>
        private JsonBoolean readBoolean() {
            CharBuffer buffer = new CharBuffer();
            char c;
            while ((c = _internalReader.Next()) != BufferedCharReader.EOF &&
                c != TerminalSymbol.COMMA &&
                c != TerminalSymbol.RIGHT_SQUARE_BRACKET &&
                c != TerminalSymbol.RIGHT_BRACE &&
                c != TerminalSymbol.SPACE &&
                c != TerminalSymbol.ENTER &&
                c != TerminalSymbol.TABLE &&
                c != TerminalSymbol.NEWLINE) {

                buffer.Push(c);
            }

            string str = buffer.GetString();

            if (str == "true") {
                return new JsonBoolean(true);
            } else if (str == "false") {
                return new JsonBoolean(false);
            } else {
                throw new SyntaxError(string.Format("The token \"{0}\" are not expected.", str), _internalReader.GetString(10));
            }
        }

        #endregion

        #region read a string

        /// <summary>
        /// read a string from raw string.
        /// </summary>
        /// <returns></returns>
        private JsonString readString() {
            char s = _internalReader.Next();
            CharBuffer buffer = new CharBuffer();

            if (!_internalReader.More()) {
                throw new SyntaxError(string.Format("The token \"{0}\" is not expected.", s), _internalReader.GetString(10));
            }

            char c = _internalReader.Next();
            char l = BufferedCharReader.EOF;

            while ((c != s || l == TerminalSymbol.BACKSLASH)) {
                buffer.Push(c);

                l = c;

                if (!_internalReader.More()) {
                    throw new SyntaxError(string.Format("The token \"{0}\" is not expected.", s), _internalReader.GetString(10));
                }

                c = _internalReader.Next();
            }

            string str = string.Empty;
            c = (char)buffer.Pop();

            if (c != BufferedCharReader.EOF) {
                buffer.Push(c);
            }

            return new JsonString(buffer.GetString());
        }

        #endregion

        #region read a datetime

        /// <summary>
        /// read a datetime from raw string.
        /// </summary>
        /// <returns></returns>
        private JsonDateTime readDateTime() {
            CharBuffer buffer = new CharBuffer();

            char c = _internalReader.Next();

            if (c == TerminalSymbol.LEFT_ANGLE_BRACKET) {
                while ((c = _internalReader.Next()) != TerminalSymbol.RIGHT_ANGLE_BRACKET) {

                    buffer.Push(c);

                    if (!_internalReader.More()) {
                        break;
                    }
                }
            } else {
                throw new SyntaxError();
            }

            string str = buffer.GetString();

            DateTime dt;

            if (DateTime.TryParse(str, out dt)) {
                return new JsonDateTime(dt);
            } else {
                throw new SyntaxError("Not datetime type.", _internalReader.GetString(10));
            }
        }

        #endregion

        #region read an array

        /// <summary>
        /// read an rarray from raw string.
        /// </summary>
        /// <returns></returns>
        private JsonArray readArray() {
            char c = _internalReader.Next();
            skipSpace();

            JsonArray array = new JsonArray();
            c = _internalReader.Next();

            while (_internalReader.More() && c != TerminalSymbol.RIGHT_SQUARE_BRACKET) {
                _internalReader.Back();
                switch (c) {
                    // '[' mean Array Array.
                    case TerminalSymbol.LEFT_SQUARE_BRACKET:
                        array.Add(readArray());
                        break;
                    // '{' mean Dict Array
                    case TerminalSymbol.LEFT_BRACE:
                        array.Add(readDictionay());
                        break;
                    // ' or " mean String Array
                    case TerminalSymbol.SINGLE_QUOTE:
                    case TerminalSymbol.DOUBLE_QUOTE:
                        array.Add(readString());
                        break;
                    case TerminalSymbol.LEFT_ANGLE_BRACKET:
                        array.Add(readDateTime());
                        break;
                    // t or f mean Boolean Array
                    case 't':
                    case 'f':
                        array.Add(readBoolean());
                        break;
                    // n mean null
                    case 'n':
                        array.Add(readNull());
                        break;
                    case TerminalSymbol.POSITIVE:
                    case TerminalSymbol.NEGATIVE:
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        array.Add(readNumber());
                        break;
                    // other is unkown
                    default:
                        // Unkown token c
                        throw new SyntaxError(string.Format("The token \"{0}\" is not expected.", c), _internalReader.GetString(10));
                }

                skipSpace();

                c = _internalReader.Next();

                if (c == TerminalSymbol.COMMA) {
                    skipSpace();
                    c = _internalReader.Next();
                }
            }

            return array;
        }

        #endregion

        #region read a dictionary

        #region read a key value pair

        /// <summary>
        /// Read a key value pair.
        /// </summary>
        /// <returns></returns>
        private KeyValuePair<string, IJsonObject> readKeyValue() {
            string key;
            IJsonObject obj;

            CharBuffer buffer = new CharBuffer();
            char c = _internalReader.Next();

            if (c == TerminalSymbol.RIGHT_BRACE) {
                return new KeyValuePair<string, IJsonObject>();
            }

            char t;
            // Begin with ' or "
            if (c == TerminalSymbol.SINGLE_QUOTE || c == TerminalSymbol.DOUBLE_QUOTE) {
                t = c;
                c = _internalReader.Next();
            } else {
                t = TerminalSymbol.COLON;
            }

            while (c != t) {
                if (c == TerminalSymbol.SPACE ||
                    c == TerminalSymbol.TABLE ||
                    c == TerminalSymbol.ENTER ||
                    c == TerminalSymbol.NEWLINE) {
                    if (t != TerminalSymbol.COLON) {
                        throw new SyntaxError(@"Key can't contain ' ', '\r', '\n' or '\t'.", _internalReader.GetString(10));
                    } else {
                        skipSpace();
                        break;
                    }
                }

                if (char.IsLetter(c) || char.IsNumber(c) || c == '_' || c == '@' || c == '$') {
                    buffer.Push(c);
                } else {
                    // Key必须是字母、数字或_
                    throw new SyntaxError(string.Format("The token \"{0}\" is not expected.", c), _internalReader.GetString(10));
                }

                if (!_internalReader.More())
                    break;

                c = _internalReader.Next();
            }

            key = buffer.GetString();

            if (c != TerminalSymbol.COLON) {
                skipSpace();
                _internalReader.Next();
            }

            skipSpace();

            c = _internalReader.Next();
            _internalReader.Back();

            switch (c) {
                //case TerminalSymbol.LEFT_PARENTHESIS:
                //    obj = ReadTuple();
                //    break;
                // '[' mean Array Array.
                case TerminalSymbol.LEFT_SQUARE_BRACKET:
                    obj = readArray();
                    break;
                // '{' mean Dict Array
                case TerminalSymbol.LEFT_BRACE:
                    obj = readDictionay();
                    break;
                // ' or " mean String Array
                case TerminalSymbol.SINGLE_QUOTE:
                case TerminalSymbol.DOUBLE_QUOTE:
                    obj = readString();
                    break;
                case TerminalSymbol.LEFT_ANGLE_BRACKET:
                    obj = readDateTime();
                    break;
                // t or f mean Boolean Array
                case 't':
                case 'f':
                    obj = readBoolean();
                    break;
                // n mean null
                case 'n':
                    obj = readNull();
                    break;
                case TerminalSymbol.POSITIVE:
                case TerminalSymbol.NEGATIVE:
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    obj = readNumber();
                    break;
                // other is unkown
                default:
                    // Unkown token c
                    throw new SyntaxError(string.Format("The token \"{0}\" is not expected.", c), _internalReader.GetString(10));
            }

            return new KeyValuePair<string, IJsonObject>(key, obj);
        }

        #endregion

        /// <summary>
        /// Read a dictionary from raw string.
        /// </summary>
        /// <returns></returns>
        private JsonDictionary readDictionay() {
            char c = _internalReader.Next();
            JsonDictionary dict = new JsonDictionary();

            skipSpace();
            c = _internalReader.Next();

            while (_internalReader.More() && c != TerminalSymbol.RIGHT_BRACE) {
                _internalReader.Back();
                KeyValuePair<string, IJsonObject> kvp = readKeyValue();
                skipSpace();

                if (string.IsNullOrEmpty(kvp.Key)) {
                    throw new SyntaxError("Key must not be null or empty.", _internalReader.GetString(10));
                }

                dict[kvp.Key] = kvp.Value;
                c = _internalReader.Next();

                if (c == TerminalSymbol.COMMA) {
                    skipSpace();
                    c = _internalReader.Next();
                }
            }

            return dict;
        }

        #endregion

        #region read null

        /// <summary>
        /// Read a JsonNull object
        /// </summary>
        /// <returns></returns>
        private JsonNull readNull() {
            CharBuffer buffer = new CharBuffer();
            char c;

            while (_internalReader.More()) {
                c = _internalReader.Next();

                if (c != TerminalSymbol.COMMA &&
                    c != TerminalSymbol.RIGHT_SQUARE_BRACKET &&
                    c != TerminalSymbol.RIGHT_BRACE &&
                    c != TerminalSymbol.SINGLE_QUOTE &&
                    c != TerminalSymbol.DOUBLE_QUOTE &&
                    c != TerminalSymbol.SPACE &&
                    c != TerminalSymbol.ENTER &&
                    c != TerminalSymbol.NEWLINE &&
                    c != TerminalSymbol.TABLE) {
                    buffer.Push(c);
                } else {
                    break;
                }
            }

            string str = buffer.GetString();
            if (str != "null") {
                throw new SyntaxError(string.Format("The token \"{0}\" is not expected.", str), _internalReader.GetString(10));
            }

            return JsonObject.Null;
        }

        #endregion

        #region skip spaces

        /// <summary>
        /// Skip spaces that contains ' ', '\t', '\r' and '\n'.
        /// </summary>
        private void skipSpace() {
            char c;
            while (_internalReader.More()) {
                c = _internalReader.Next();

                if (c == TerminalSymbol.SPACE ||
                    c == TerminalSymbol.TABLE ||
                    c == TerminalSymbol.ENTER ||
                    c == TerminalSymbol.NEWLINE)
                    continue;
                else {
                    _internalReader.Back();
                    return;
                }
            }
        }

        #endregion
    }
}